package clases.tabu;
import interfaces.TabuSearch;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import clases.CasoDePrueba;
import clases.Helper;
import clases.Pair;
import clases.ResultadoCasoDePruebaArrayBinarios;
import clases.Tupla;


public class TabuSearch2Variables implements TabuSearch{
	
	int MAX_ITERACIONES = 10;
	int MAX_CANT_ITERACIONES_SIN_MEJORAR = 5;
	int TAM_MAX = 10;

	static ArrayList<CasoDePrueba> casosDePrueba = new ArrayList<CasoDePrueba>();
	
	public TabuSearch2Variables(int iter, int cantIterSinMejorar, int tamListaTabu) {
		MAX_ITERACIONES = iter;
		MAX_CANT_ITERACIONES_SIN_MEJORAR = cantIterSinMejorar;
		TAM_MAX = tamListaTabu;		
	}
	
	
	public static void main(String[] args) throws IOException {
		TabuSearch2Variables tabu = new TabuSearch2Variables();
		tabu.doit();
	}
	public TabuSearch2Variables() {
		
	}
	
	
	public void doit() throws IOException {
		Helper.leerArchivoTabuSearch(this, "TP3TabuSearch.out");
	}

	
	@SuppressWarnings("unchecked")
	public ResultadoCasoDePruebaArrayBinarios dameLaSolucionDelCaso(CasoDePrueba caso, ResultadoCasoDePruebaArrayBinarios constructiva, BufferedWriter bufferCriterioParada) throws IOException {
			// Llenamos la matriz de Clausulas para saber las variables que existen en cada una
		int[][] matrizClausulas = Helper.llenarPositivosYNegativosMatriz(caso.clausulas, caso.clausulas.size(),	constructiva.binarioQueHaceMaxLasClausulas.length);

			// esto lo va a explicar Martin
		int[] contadorVariablesEnTrueMax = Helper.contarValoresVerdaderos(matrizClausulas, constructiva.binarioQueHaceMaxLasClausulas, constructiva.binarioQueHaceMaxLasClausulas.length);

			// solucion temporaria maxima de la vecindad
		ResultadoCasoDePruebaArrayBinarios solucionMax = constructiva;
			// solucion definitiva
		ResultadoCasoDePruebaArrayBinarios solucionDefinitiva = new ResultadoCasoDePruebaArrayBinarios(solucionMax.clausulasMax, solucionMax.binarioQueHaceMaxLasClausulas.clone(),	new ArrayList<Integer>(solucionMax.listaClausulasSatisfechas));
			// solucion inicial que nos da la busqueda local
		int[] solucionBusquedaLocal = solucionMax.binarioQueHaceMaxLasClausulas.clone();
			// cada solucion de la vecindad
		Tupla solucionesNuevas = new Tupla();
		Tupla solucionMejorHastaAhora = new Tupla();
			// lista tabu que la inicializamos en -1 para saber cuando terminamos (caso base)
		Pair tupla = new Pair(-1, -1);
		Map<Pair, Integer> listaTabu = new HashMap<Pair, Integer>();

		boolean mejore = true;
		boolean hiceVerdaderasTodasLasClausulas = false;
		//boolean apendieMejora = false;
		int cantidadDeIteracionesSinMejorar = 0;

		// elemento la listaTabu
		int cantidadDeIteraciones = 0;

		while (mejore && !hiceVerdaderasTodasLasClausulas && cantidadDeIteracionesSinMejorar <= MAX_CANT_ITERACIONES_SIN_MEJORAR && cantidadDeIteraciones <= MAX_ITERACIONES) {
			// Condiciones de corte
			
			cantidadDeIteracionesSinMejorar++;
			mejore = false; // Reseteo mejore, para volver a centrar en la nueva solucion y explorar toda la vecindad

			boolean encontreLaPrimeraMejor = false;

			Pair tabuTemporal = new Pair(-1, -1);
			// prendemos y apagamos cada variable ... exploramos la vecindad
			for (int i = 0; i < solucionBusquedaLocal.length && !encontreLaPrimeraMejor; i++) {
				for (int j = 0; j < solucionBusquedaLocal.length && !encontreLaPrimeraMejor; j++) {
					 
					// chequeo para no permitir movimientos tabu
					tupla = new Pair(i, j);

					if (!listaTabu.keySet().contains(tupla)) {
						// Miro cambiando una sola variable, por ahora
						solucionesNuevas = Helper.chequear2Variables(solucionMax, i, j, caso, contadorVariablesEnTrueMax, matrizClausulas);
						
						if (solucionesNuevas.clausulasSatisfechas > solucionMejorHastaAhora.clausulasSatisfechas) {
							tabuTemporal = new Pair(i, j);
							solucionMejorHastaAhora.clausulasSatisfechas = solucionesNuevas.clausulasSatisfechas;
							solucionMejorHastaAhora.binarion = solucionesNuevas.binarion.clone();
							solucionMejorHastaAhora.listaClausulas = (ArrayList<Integer>) solucionesNuevas.listaClausulas.clone();
							solucionMejorHastaAhora.contadorVariablesEnTrue = solucionesNuevas.contadorVariablesEnTrue.clone();
							
							// Encontre una solucion mejor hasta el momento
							
 
						}
						
						// si la solucion Nueva es mejor que solucionMax me quedo con ella
						if (solucionesNuevas.clausulasSatisfechas > solucionMax.clausulasMax) {
							tabuTemporal = new Pair(i, j);
							
							solucionMax.clausulasMax = solucionesNuevas.clausulasSatisfechas;
							solucionMax.binarioQueHaceMaxLasClausulas = solucionesNuevas.binarion.clone();
							solucionMax.listaClausulasSatisfechas = (ArrayList<Integer>) solucionesNuevas.listaClausulas.clone();
							contadorVariablesEnTrueMax = solucionesNuevas.contadorVariablesEnTrue.clone();
							
							// Encontre una solucion mejor, centro mi vecindad en la mejor solucion
							mejore = true;
							encontreLaPrimeraMejor = true;
							cantidadDeIteracionesSinMejorar = 0;

							// guardo en solucionDefinitiva la mejor que tengo hasta el momento
							if (solucionMax.clausulasMax > solucionDefinitiva.clausulasMax) {
								solucionDefinitiva.clausulasMax = solucionMax.clausulasMax;
								solucionDefinitiva.binarioQueHaceMaxLasClausulas = solucionMax.binarioQueHaceMaxLasClausulas.clone();
								solucionDefinitiva.listaClausulasSatisfechas = (ArrayList<Integer>) solucionMax.listaClausulasSatisfechas.clone();
//								bufferCriterioParada.append("Clausulas Satisfechas: " + new Integer(solucionDefinitiva.clausulasMax).toString() + " --- ");
//								bufferCriterioParada.append("Cantidad de Iteraciones: " + new Integer(cantidadDeIteraciones).toString());
//								bufferCriterioParada.newLine();
//								apendieMejora = true;
								if (caso.c == solucionDefinitiva.clausulasMax) {
									hiceVerdaderasTodasLasClausulas = true;
								}
							}
						}
					}
				}
			}
			// si mejore añiado el movimiento a la listaTabu
			if (!mejore) {
				cantidadDeIteraciones++;
			}
			
			listaTabu = mantenerListaTabu(listaTabu, tabuTemporal);
			
			
//			}
			// sino permito un movimiento tabu y guardo este movimiento en pop
//			else {
				
//				Pair tabuPermitido = this.getRamdomPair(listaTabu);
//				//Si tabuPermitido es null, no tenemos elemento tabu a permitir, entonces terminamos
//				if (tabuPermitido != null) {
//					solucionesNuevas = Helper.chequear2Variables(solucionMax, tabuPermitido.getPrim(), tabuPermitido.getSec(), caso, contadorVariablesEnTrueMax, matrizClausulas);
					if(solucionMejorHastaAhora.binarion!=null){
						solucionMax.clausulasMax = solucionMejorHastaAhora.clausulasSatisfechas;
						solucionMax.binarioQueHaceMaxLasClausulas = solucionMejorHastaAhora.binarion.clone();
						solucionMax.listaClausulasSatisfechas = (ArrayList<Integer>) solucionMejorHastaAhora.listaClausulas.clone();
						contadorVariablesEnTrueMax = solucionMejorHastaAhora.contadorVariablesEnTrue.clone();
						mejore = true;
						
						solucionMejorHastaAhora = new Tupla();
					
					}
//				} else {
//					// si estoy en la primer vlistaTabu.put(tabuTemporal, numeroVecesCayoAca + 1);ecindad es porque llegue alprincipio y no pude mejorar
//					// TODO elegir un movimiento al azar para poder arrancar
//					// desde otra solucion ... esto lo vamos a ver bien mediante
//					// pruebas
//					break;
//				}

//			}
		}
//		if(!apendieMejora) {
//			bufferCriterioParada.append("No hubo mejoras en la sol definitiva --- ");
//			bufferCriterioParada.append("Cantidad de Clausulas Satisfechas: " + new Integer(solucionMax.clausulasMax).toString());
//			bufferCriterioParada.newLine();
//		}
		
		
		return solucionDefinitiva;
	}

	/**
	 * @param listaTabu
	 * @param tabuTemporal
	 * @return se modifica la listaTabu pasada como parametro, y la devuelve
	 */
	private Map<Pair, Integer> mantenerListaTabu(Map<Pair, Integer> listaTabu, Pair tabuTemporal) {
		if(listaTabu.keySet().size() > TAM_MAX){
			listaTabu  = desalojar(listaTabu, listaTabu.keySet().size() / 2);
		}
		
		Integer numeroVecesCayoAca = 0;		
		if (listaTabu.get(tabuTemporal) != null) {
			numeroVecesCayoAca = listaTabu.get(tabuTemporal);
		}
		
		listaTabu.put(tabuTemporal, numeroVecesCayoAca + 1);
		return listaTabu;
	}

	private Map<Pair, Integer> desalojar(Map<Pair, Integer> listaTabu, int cantidad) {
		Map<Pair, Integer> nuevoMap = new HashMap<Pair, Integer>(cantidad);
		Iterator<Pair> it = listaTabu.keySet().iterator();
		while (cantidad > 0 && it.hasNext()){
			cantidad--;
			Pair actual = it.next();
			nuevoMap.put(actual, listaTabu.get(actual));
		}
		return nuevoMap;
	}

	private Pair getRamdomPair(Map<Pair, Integer> listaTabu) {
		int keysSizes = listaTabu.keySet().size();
		if (keysSizes > 0) {
			int randomPos = (int) Math.floor(Math.random() * keysSizes);
			Iterator<Pair> it = listaTabu.keySet().iterator();
			int i = 0;
			while (it.hasNext()) {
				Pair par = it.next();
				if (i == randomPos) {
					return par;
				}
				i++;
			}
		}
		return null;
	}
}
